<script setup>
import { ref, reactive } from 'vue'
import axios from 'axios'
import KLineChart from './components/KLineChart.vue'
import ForecastChart from './components/ForecastChart.vue'

// 回测参数
const backtestParams = ref({
  code: '600519',
  start_date: '2022-01-01',
  end_date: '2023-12-31',
  strategy_name: 'ma_cross',
  initial_cash: 100000.0,
  position_sizer: 95, // 仓位比例，单位 %
  commission_rate: 0.0003, // 佣金率
  // asset_type is removed
});

const backtestResult = ref(null);
const isLoading = ref(false);
const errorMessage = ref('');

// 策略和指标的解释
const explanations = {
  strategies: {
    ma_cross: '双均线交叉策略：当短期移动平均线（如20日线）从下向上穿过长期移动平均线（如50日线）时，形成“金叉”，买入股票。反之，当短期线从上向下穿过长期线，形成“死叉”，卖出股票。这是一种经典的趋势跟踪策略。',
    rsi: '相对强弱指数策略：RSI是一个动量指标，衡量股价近期上涨和下跌的幅度。当RSI低于预设的超卖阈值（如30）时，认为股价可能已超跌，买入。当RSI高于超买阈值（如70）时，认为股价可能已超涨，卖出。',
    buy_and_hold: '买入并持有策略：在回测周期的第一天，用全部资金买入该股票，并一直持有到周期结束。这是一个被动的基准策略，用于衡量主动型策略是否创造了超额收益（Alpha）。',
    bollinger: '布林带策略：利用统计学原理，围绕移动平均线构建一个价格通道。当股价触及下轨时，被视为超卖信号，买入；当股价触及上轨时，被视为超买信号，卖出。适用于震荡行情。',
    macd: 'MACD趋势增强策略：在传统MACD策略（快线上穿慢线买入）的基础上，增加了一个基本面过滤器。只有当MACD出现“金叉”信号，且该股票最近一期财报的“净利润同比增长率”为正数时，才会执行买入。这旨在过滤掉那些没有基本面支撑的技术性买入信号。'
  },
  metrics: {
    '夏普比率': '衡量承受每单位风险所获得的超额回报。比率越高，说明在承受同样风险的情况下，回报越高，投资组合表现越好。负值表示投资组合表现不如无风险资产（如国债）。',
    '最大回撤(%)': '在选定周期内，资产净值从最高点到随后最低点的最大跌幅。这个指标衡量了策略可能面临的最糟糕的亏损情况，是重要的风险指标。',
    '胜率(%)': '在所有完成的交易中，盈利交易次数所占的百分比。高胜率并不一定意味着高收益，需要结合盈亏比等其他指标一起看。'
  }
};

const runBacktest = async () => {
  isLoading.value = true;
  errorMessage.value = '';
  backtestResult.value = null;
  // 需要再fronte目录下创建.envw文件，用于获取地址
  const apiUrl = import.meta.env.VITE_API_URL;

  try {
    console.log('开始发送回测请求...', backtestParams.value);
    const response = await axios.post(`${apiUrl}/api/backtest/`, backtestParams.value);
    console.log('回测请求成功，响应数据:', response.data);
    
    if (response.data.error) {
      errorMessage.value = response.data.error;
    } else {
      backtestResult.value = response.data;
    }

  } catch (error) {
    console.error('回测请求失败:', error);
    errorMessage.value = 'Failed to run backtest. Check console for details.';
  } finally {
    isLoading.value = false;
  }
}

const formatValue = (value, key) => {
  if (typeof value === 'number') {
    if (key.includes('(%)') || key.includes('收益率') || key.includes('胜率')) {
      return `${value.toFixed(2)}%`;
    }
    if (key.includes('资金') || key.includes('资产') || key.includes('盈亏') || key.includes('金额')) {
      return `¥${value.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
    }
    if (key.includes('夏普比率')) {
        return value.toFixed(3);
    }
    return value;
  }
  return value;
}

// --- Forecasting State ---
const forecastParams = ref({ code: '600519', days_to_predict: 30 });
const forecastResult = ref(null);
const isForecasting = ref(false);
const forecastError = ref('');

// --- Recent Prices State ---
const recentPrices = ref(null);
const recentDays = ref(10); // Default to show recent 10 days
const isFetchingRecent = ref(false);

const runForecastAndFetchRecent = async () => {
  // Reset states
  isForecasting.value = true;
  isFetchingRecent.value = true;
  forecastError.value = '';
  forecastResult.value = null;
  recentPrices.value = null;
  forecastParams.value.code = backtestParams.value.code;
  const apiUrl = import.meta.env.VITE_API_URL || 'http://101.126.8.228:8000';

  console.log('开始发送预测请求...', forecastParams.value);
  // Fire both requests in parallel
  const forecastPromise = axios.post(`${apiUrl}/api/forecast/`, forecastParams.value);
  const recentPromise = axios.get(`${apiUrl}/api/stock/${forecastParams.value.code}/recent?days=${recentDays.value}`);

  try {
    const [forecastResponse, recentResponse] = await Promise.all([forecastPromise, recentPromise]);
    console.log('预测请求成功，响应数据:', forecastResponse.data);

    // Handle forecast result
    if (forecastResponse.data.error) {
      forecastError.value = forecastResponse.data.error;
    } else {
      forecastResult.value = forecastResponse.data;
    }

    // Handle recent prices result
    if (recentResponse.data.error) {
       // Append error to the main forecast error display
       forecastError.value += ` | 近期股价获取失败: ${recentResponse.data.error}`;
    } else {
      recentPrices.value = recentResponse.data.reverse(); // Show newest date first
    }

  } catch (error) {
    console.error('预测请求失败:', error);
    forecastError.value = '预测或获取近期股价时发生严重错误。';
  } finally {
    isForecasting.value = false;
    isFetchingRecent.value = false;
  }
};

// A new helper to get a subset of forecast data for display
const getDisplayForecast = (result) => {
  if (!result || !result.forecast_data) return [];
  // Show last 5 days of history + next N days of forecast
  const historical = result.historical_data;
  const forecast = result.forecast_data;
  const lastHistoryDate = historical[historical.length - 1].ds;
  const forecastStartIndex = forecast.findIndex(item => item.ds > lastHistoryDate);
  return forecast.slice(forecastStartIndex);
};

</script>

<template>
  <div id="app-container">
    <header>
      <h1>📈 量化回测系统</h1>
    </header>

    <main>
      <section class="card">
        <h2>开始一次新回测</h2>
        <div class="form-grid">
          <div>
            <label for="code">股票代码:</label>
            <input id="code" v-model="backtestParams.code" />
          </div>
          <div>
            <label for="start-date">开始日期:</label>
            <input id="start-date" type="date" v-model="backtestParams.start_date" />
          </div>
          <div>
            <label for="end-date">结束日期:</label>
            <input id="end-date" type="date" v-model="backtestParams.end_date" />
          </div>
          <div class="tooltip-container" :data-tooltip="explanations.strategies[backtestParams.strategy_name]">
            <label for="strategy">选择策略:</label>
            <select id="strategy" v-model="backtestParams.strategy_name">
              <option value="ma_cross">双均线交叉策略</option>
              <option value="rsi">相对强弱指数策略</option>
              <option value="bollinger">布林带策略</option>
              <option value="macd">MACD策略</option>
              <option value="buy_and_hold">买入并持有 (基准)</option>
            </select>
          </div>
          <div>
            <label for="cash">初始资金:</label>
            <input id="cash" type="number" v-model.number="backtestParams.initial_cash" />
          </div>
          <div class="form-control">
              <label for="sizer">仓位比例 (%):</label>
              <input id="sizer" type="number" v-model.number="backtestParams.position_sizer" min="1" max="100" />
          </div>
          <div class="form-control">
              <label for="commission">佣金率 (如0.03%填0.0003):</label>
              <input id="commission" type="number" v-model.number="backtestParams.commission_rate" step="0.00001" />
          </div>
        </div>
        <button @click="runBacktest" :disabled="isLoading" class="run-button">
          {{ isLoading ? '运行回测中...' : '运行回测' }}
        </button>
      </section>

      <section class="card result-card" v-if="backtestResult || errorMessage">
        <h2>回测结果</h2>
        <div v-if="errorMessage" class="error-message">{{ errorMessage }}</div>
        <div v-if="backtestResult" class="result-header">
          <a :href="`/company_info.html?code=${backtestResult['股票代码']}`" target="_blank" class="stock-title-link">
            {{ backtestResult['股票名称'] }} ({{ backtestResult['股票代码'] }})
          </a>
        </div>
        <div class="result-grid">
          <div class="result-item" v-for="(value, key) in backtestResult" :key="key">
            <div class="tooltip-container" :data-tooltip="explanations.metrics[key]">
                <span class="result-key">{{ key }}</span>
            </div>
            <span class="result-value">{{ formatValue(value, key) }}</span>
          </div>
        </div>
        <KLineChart
          v-if="backtestResult && backtestResult.klineData"
          :klineData="backtestResult.klineData"
          :signals="backtestResult.signals"
          :equityCurve="backtestResult.equityCurve"
        />
      </section>

      <hr class="section-divider">

      <section class="card">
        <h2>未来走势预测 & 近期股价</h2>
        <div class="form-grid">
          <div class="form-control">
            <label for="forecast-code">股票代码 (将使用上方回测区的代码):</label>
            <input id="forecast-code" :value="backtestParams.code" disabled />
          </div>
          <div class="form-control">
            <label for="forecast-days">预测未来天数:</label>
            <input id="forecast-days" type="number" v-model.number="forecastParams.days_to_predict" />
          </div>
           <div class="form-control">
            <label for="recent-days">显示最近天数:</label>
            <input id="recent-days" type="number" v-model.number="recentDays" />
          </div>
        </div>
        <button @click="runForecastAndFetchRecent" :disabled="isForecasting" class="run-button forecast-button">
          {{ isForecasting ? '正在处理...' : '开始预测 & 获取近期股价' }}
        </button>
        <ForecastChart
          v-if="forecastResult && forecastResult.historical_data && forecastResult.forecast_data"
          :historical="forecastResult.historical_data"
          :forecast="forecastResult.forecast_data"
        />
      </section>
    </main>
  </div>
</template>

<style scoped>
header {
  line-height: 1.5;
  border-bottom: 1px solid #ddd;
  margin-bottom: 2rem;
}
.card {
  background: #f9f9f9;
  border: 1px solid #eee;
  border-radius: 5px;
  padding: 1.5rem;
  margin-bottom: 1rem;
}
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 1rem 1.5rem; /* row-gap column-gap */
  align-items: end; /* This will align items to the bottom of the cell */
}
.form-grid > div {
  /* No need for flex here anymore */
}
label {
  margin-bottom: 0.5rem;
  font-weight: bold;
}
input, select {
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  height: 38px; /* Give a fixed height to align */
  box-sizing: border-box; /* Important for padding and border */
}

input[disabled] {
  background-color: #e9ecef;
  opacity: 1;
  cursor: not-allowed;
}
.run-button {
  padding: 0.75rem 1.5rem;
  cursor: pointer;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  margin-top: 1.5rem; /* Adds space above the button */
}
button:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}
.error-message {
  color: red;
  margin-bottom: 1rem;
}
.result-card {
  border-left: 5px solid #007bff;
}
.result-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.2rem;
}
.result-item {
  background-color: #fff;
  border-radius: 6px;
  padding: 1rem;
  box-shadow: 0 2px 4px rgba(0,0,0,0.05);
  display: flex;
  flex-direction: column;
}
.result-key {
  font-weight: bold;
  color: #555;
  font-size: 0.9rem;
  margin-bottom: 0.5rem;
  display: flex;
  align-items: center;
}
.result-key .info-tooltip {
  margin-left: 0.5rem;
}
.result-value {
  font-size: 1.5rem;
  color: #007bff;
  font-weight: 300;
}
.strategy-selection {
  grid-column: span 2; /* Make it span across two columns */
}
.select-with-info {
  position: relative;
  display: flex;
  align-items: center;
}
.select-with-info .info-tooltip {
  position: absolute;
  top: 100%;
  left: 0;
  background-color: #333;
  color: white;
  padding: 0.5rem 0.8rem;
  border-radius: 4px;
  font-size: 0.8rem;
  line-height: 1.4;
  z-index: 1000;
  white-space: nowrap;
  max-width: 300px; /* Limit tooltip width */
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
  margin-top: 0.5rem;
}
.select-with-info .info-tooltip::after {
  content: "";
  position: absolute;
  top: -5px;
  left: 10px;
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-bottom: 5px solid #333;
}
.info-icon {
  margin-left: 0.5rem;
  font-size: 0.8rem;
}
.tooltip-text {
  margin: 0;
  padding: 0;
}
.stock-title-link {
  font-size: 1.2rem;
  font-weight: bold;
  color: #007bff;
  text-decoration: none;
  margin-bottom: 1rem;
  position: relative; /* Needed for positioning context */
}
.stock-title-link:hover {
  text-decoration: underline;
}
/* New styles for the hover card */
.info-hover-card {
  position: fixed;
  z-index: 1000;
  background-color: #fff;
  border: 1px solid #ddd;
  border-radius: 8px;
  box-shadow: 0 4px 15px rgba(0,0,0,0.15);
  padding: 1.5rem;
  width: auto;
  min-width: 400px;
  max-width: 600px;
}
.info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1rem;
  margin-bottom: 1.5rem;
}
.info-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.8rem 1rem;
  background-color: #f0f0f0;
  border-radius: 5px;
  font-size: 0.9rem;
  color: #555;
}
.info-key {
  font-weight: bold;
  color: #333;
}
.info-value {
  font-weight: 500;
  color: #007bff;
}
/* Add new tooltip styles */
.tooltip-container {
  position: relative;
  display: inline-block;
}
.tooltip-container:hover::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-bottom: 5px;
  background-color: #333;
  color: #fff;
  padding: 8px 12px;
  border-radius: 4px;
  font-size: 0.85rem;
  line-height: 1.4;
  white-space: pre-wrap;
  width: 300px;
  max-width: 90vw;
  z-index: 100;
  pointer-events: none;
  opacity: 0.95;
}
.result-key {
    cursor: help;
}
.section-divider {
  border: none;
  border-top: 2px solid #eee;
  margin: 2.5rem 0;
}
.forecast-button {
  background-color: #28a745;
}
.forecast-button:hover {
  background-color: #218838;
}
.form-control {
  display: flex;
  flex-direction: column;
}
.table-container {
  width: 100%;
  overflow-x: auto;
  max-height: 400px;
  overflow-y: auto;
  border: 1px solid #dee2e6;
  border-radius: 6px;
}
table {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
}
th, td {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid #dee2e6;
}
thead tr {
  background-color: #f8f9fa;
}
tbody tr:nth-of-type(even) {
  background-color: #f8f9fa;
}
tbody tr:hover {
  background-color: #e9ecef;
}
.two-column-layout {
  display: flex;
  flex-wrap: wrap;
  gap: 2rem;
}
.column {
  flex: 1;
  min-width: 300px;
}
</style> 